package xj.graph2d;

import static xj.graph2d.DrawShapeConstants.EDITING;
import static xj.graph2d.DrawShapeConstants.NORMAL;
import static xj.graph2d.DrawShapeConstants.POPUP;
import static xj.graph2d.DrawShapeConstants.PointInside;
import static xj.graph2d.DrawShapeConstants.PointOnStringBaselineEnd;
import static xj.graph2d.DrawShapeConstants.PointOnStringBaselineStart;
import static xj.graph2d.DrawShapeConstants.PointOutside;
import static xj.graph2d.DrawShapeConstants.cursorcolor;
import static xj.graph2d.DrawShapeConstants.d;
import static xj.util.xml.XMLUtil.getXMLAttributeString;
import static xj.util.xml.XSLTUtil.filterXMLText;

import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;

import xj.graph2d.util.DrawingUtilities;
import xj.lang.CodeWriter;
import xj.util.gui.Point2DFloat;
import xj.util.xml.XMLUtil;

public class TextShape 
  extends LabelBase 
  implements Rotatable {

  public TextShape() {
    this(0, 0, null);
  }

  public TextShape(String s) {
    this(0, 0, s);
  }

  public TextShape(float x, float y, String s) {
    string = new StringBuilder();
    if (s != null) {
      string.append(s);
    }
    pos = new Point2DFloat(x, y);
    calculateBounds();

    if (debug) {
      System.out.println("TextShape() string=\"" + string.toString() + 
			 "\" x1=" + p1.getX() + " y1=" + p1.getY() + 
			 " x2=" + p2.getX() + " y2=" + p2.getY());
    }

  }

  public TextShape(String s, String prefix, String suffix) {
    this(0, 0, s, prefix, suffix);
  }

  public TextShape(float x, float y, String s, String prefix, String suffix) {
    string = new StringBuilder();
    if (prefix != null) {
      this.prefix = prefix;
      lenPrefix = prefix.length();
      string.append(prefix);
    }
    if (s != null) {
      string.append(s);
    }
    if (suffix != null) {
      this.suffix = suffix;
      lenSuffix = suffix.length();
      string.append(suffix);
    }
    cursor = lenPrefix;
    pos = new Point2DFloat(x, y);
    // pos = new Point2DFloat();
    calculateBounds();
  }

  public DrawShape makeInstance() {
    return new TextShape();
  }

  @Override
  public DrawShape cloneDrawShape(boolean deepClone) 
    throws CloneNotSupportedException {
    String str = string.toString();
    int len = str.length();
    str = str.substring(lenPrefix, len - lenSuffix);
    TextShape s = new TextShape((float) pos.getX(), (float) pos.getY(), str,
				prefix, suffix);
    copyTo(s);
    cloneModel(s);
    if (angle != 0) {
      s.setRotationAngle(angle);
    }
    s.boxPrototype = boxPrototype;
    s.prompt = prompt;
    s.allowMultiLine = allowMultiLine;
    s.markString = markString;
    return s;
  }

  @Override
  public String getName() {
    return "Text";
  }

  public boolean isAllowMultiLine() {
    return allowMultiLine;
  }

  public void setAllowMultiLine(boolean allowMultiLine) {
    this.allowMultiLine = allowMultiLine;
  }

  public boolean isEmpty() {
    return (length() == (lenPrefix + lenSuffix));
  }

  public TextShape setString(String s) {
    string = new StringBuilder();
    if (prefix != null) {
      string.append(prefix);
    }
    if (s != null) {
      string.append(s);
    }
    if (suffix != null) {
      string.append(suffix);
    }
    int last = string.length() - lenSuffix;
    if (cursor > last) {
      cursor = last;
    }
    calculateBounds();
    // fireLabelChanged();
    return this;
  }

  public void setPrefix(String prefix) { 
    String s = getString();
    this.prefix = prefix;
    if (prefix != null) { 
      lenPrefix = prefix.length();
    } else { 
      lenPrefix = 0;
    }
    cursor = lenPrefix;
    setString(s);
  }

  public void setSuffix(String suffix) { 
    String s = getString();
    this.suffix = suffix;
    if (suffix != null) { 
      lenSuffix = suffix.length();
    } else { 
      lenSuffix = 0;
    }
    cursor = lenPrefix;
    setString(s);
  }

  public boolean matchingPrefixSuffix(TextShape other) { 
    if (other != null) { 
      if (prefix == null) { 
	if (other.prefix != null) {
	  return false;
	}
      } else {
	if (!prefix.equals(other.prefix)) { 
	  return false; 
	}
      }
      if (suffix == null) { 
	if (other.suffix != null) { 
	  return false;
	}
      } else { 
	if (!suffix.equals(other.suffix)) { 
	  return false; 
	}
      }
      return true; 
    }
    return false; 
  }

  public TextShape append(char c) {
    cursor = string.length() - lenSuffix;
    string.insert(cursor++, c);
    // string.append(c);
    calculateBounds();
    return this;
  }

  public TextShape append(String s) {
    cursor = string.length() - lenSuffix;
    string.insert(cursor++, s);
    // string.append(s);
    calculateBounds();
    return this;
  }

  public TextShape insert(int i, char c) {
    if (i < lenPrefix) {
      i = lenPrefix;
    }
    int last = string.length() - lenSuffix;
    if (i > last) {
      i = last;
    }
    string.insert(i, c);
    calculateBounds();
    return this;
  }

  public TextShape insert(char c) {
    string.insert(cursor++, c);
    calculateBounds();
    return this;
  }

  public TextShape setChar(int i, char c) {
    if (i < lenPrefix) {
      i = lenPrefix;
    }
    int last = string.length() - lenSuffix;
    if (i > last) {
      i = last;
    }
    string.setCharAt(i, c);
    calculateBounds();
    return this;
  }

  public TextShape setChar(char c) {
    if (cursor < string.length()) {
      string.setCharAt(cursor++, c);
    } else {
      string.insert(cursor++, c);
    }
    calculateBounds();
    return this;
  }

  public TextShape deleteChar() {
    int last = string.length() - lenSuffix;
    if (cursor >= lenPrefix && cursor < last) {
      string.deleteCharAt(cursor);
      calculateBounds();
    }
    return this;
  }

  public TextShape backspace() {
    if (cursor > lenPrefix) {
      string.deleteCharAt(--cursor);
      calculateBounds();
    }
    return this;
  }

  @Override
  public DrawShape setCursorLeftEnd() {
    cursor = lenPrefix;
    return this;
  }

  @Override
  public DrawShape setCursorRightEnd() {
    cursor = string.length() - lenSuffix;
    return this;
  }

  @Override
  public int getCursor() {
    return cursor;
  }

  @Override
  public DrawShape setCursor(int i) {
    int last = string.length() - lenSuffix;
    if (i >= lenPrefix && i <= last) {
      cursor = i;
    } else if (i < lenPrefix) {
      cursor = lenPrefix;
    } else {
      cursor = last;
    }
    return this;
  }

  public int findPosition(Point2D p) {
    if (string.length() > 0) {
      FontMetrics fm = getFontMetrics();
      int ascent = fm.getAscent();
      int descent = fm.getDescent();
      StringBuilder temp = new StringBuilder();
      float d = 0;
      if (at == null) {
	d = (float) (p.getX() - pos.getX());
      } else {
	float tx = (float) (p.getX() - pos.getX());
	float ty = (float) (p.getY() - pos.getY());
	float[] src = { tx, ty };
	float[] dst = new float[2];
	if (inv != null) {
	  inv.transform(src, 0, dst, 0, 1);
	  d = dst[0];
	}
      }
      int l = string.length();
      int i;
      for (i = 0; i < l; i++) {
	temp.append(string.charAt(i));
	int len = fm.stringWidth(temp.toString());
	if (len >= d) {
	  break;
	}
      }
      int last = string.length() - lenSuffix;
      if (i < lenPrefix) {
	i = lenPrefix;
      }
      if (i > last) {
	i = last;
      }
      return i;
    } else {
      return lenPrefix;
    }
  }

  public DrawShape setCursor(Point p) {
    cursor = findPosition(p);
    return this;
  }

  @Override
  public void incCursor() {
    if (cursor < string.length() - lenSuffix) {
      cursor++;
    }
  }

  @Override
  public void decCursor() {
    if (cursor > lenPrefix) {
      cursor--;
    }
  }

  public boolean cursorAtBeginning() { 
    return (cursor <= lenPrefix); 
  }

  public boolean cursorAtEnd() { 
    return (cursor >= string.length() - lenSuffix);
  }

  public String getWholeString() {
    return string.toString();
  }

  public String getString() {
    String str = string.toString();
    int len = str.length();
    str = str.substring(lenPrefix, len - lenSuffix);
    return str;
  }

  public String getDisplayText() { 
    return getString();
  }

  public String getText() {
    return getString();
  }

  public void setText(String s) {
    setString(s);
  }

  public void remove() {
    if (nextLine != null) {
      nextLine.setPrevLine(prevLine);
    }
    if (prevLine != null) {
      prevLine.setNextLine(nextLine);
    }
  }

  public void insertAfter(TextShape shape) {
    if (shape != null) {
      TextShape after = nextLine;
      nextLine = shape;
      shape.prevLine = this;
      if (after != null) {
	shape.nextLine = after;
	after.prevLine = shape;
      }
    }
  }

  public void insertBefore(TextShape shape) {
    if (shape != null) {
      TextShape before = prevLine;
      prevLine = shape;
      shape.nextLine = this;
      if (before != null) {
	shape.prevLine = before;
	before.nextLine = shape;
      }
    }
  }

  public TextShape split() {
    String str = string.toString();
    int len = str.length();
    TextShape shape;
    try { 
      shape = (TextShape) clone();
    } catch (CloneNotSupportedException e) {
      shape = new TextShape();
    }
    shape.setString(str.substring(cursor, len - lenSuffix));
    shape.setCursor(lenPrefix);
    setString(str.substring(lenPrefix, cursor));
    calculateBounds();
    shape.calculateBounds();
    return shape;
  }

  public TextShape splitAtCursor() {
    return split(cursor);
  }

  public TextShape split(int i) {
    String s = string.toString();
    int c = cursor;
    TextShape shape;
    try {
      shape = (TextShape) clone();
    } catch (CloneNotSupportedException e) {
      shape = new TextShape();
    }
    shape.setString(s.substring(i));
    setString(s.substring(0, i));
    if (c > i) {
      shape.setCursor(c - i);
      setCursor(0);
    } else {
      shape.setCursor(0);
    }
    calculateBounds();
    shape.calculateBounds();
    return shape;    
  }

  public TextShape merge(TextShape s) {
    if (s != null &&
	matchingPrefixSuffix(s)) { 
      if (suffix == null) { 
	string.append(s.getString());
      } else { 
	string.insert(string.length() - lenSuffix, s.getString());
      }
      nextLine = s.nextLine;
      if (s.nextLine != null) {
	s.nextLine.prevLine = this;
      }
      calculateBounds();
    }
    return this;
  }

  @Override
  public void move(float dx, float dy) {
    pos.setLocation(pos.getX() + dx, pos.getY() + dy);
    calculateBounds();
  }

  public Point2D getBasePosition() {
    return pos;
  }

  public void setBasePosition(Point2D p) {
    if (p != null) {
      setBasePosition((float) p.getX(), (float) p.getY());
    }
  }

  public void setBasePosition(float x, float y) {
    float dx = x - (float) pos.getX();
    float dy = y - (float) pos.getY();
    move(dx, dy);
  }

  public void setBasePosition(Point2D p, boolean recurse) {
    setBasePosition(p);
    if (recurse) {
      if (nextLine != null) {
	Point2D newPos = new Point2DFloat((float) p.getX(), (float) p.getY() + height);
	nextLine.setBasePosition(newPos, true);
      }
    }
  }

  public void setPosition(Point2D p, boolean recurse) {
    setPosition(p);
    if (recurse) {
      if (nextLine != null) {
	Point2D newPos = new Point2DFloat((float) p.getX(), (float) p.getY() + height);
	nextLine.setPosition(newPos, true);
      }
    }
  }

  @Override
  public void setEndsAdj(float x1, float y1, float x2, float y2) {
    setEnds(x1, y1, x2, y2);
  }

  @Override
  public void setEnds(float x1, float y1, float x2, float y2) {

    if (debug) {
      System.out.println("TextShape.setEnds() @1 string=\"" + string.toString() + 
			 "\" x1=" + x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2);
    }

    float xmin = (float) Math.min(p1.getX(), p2.getX());
    float ymin = (float) Math.min(p1.getY(), p2.getY());
    float xmax = (float) Math.max(p1.getX(), p2.getX());
    float ymax = (float) Math.max(p1.getY(), p2.getY());

    float x0 = Math.min(x1, x2);
    float y0 = Math.min(y1, y2);
    float posx = (float) pos.getX();
    float posy = (float) pos.getY();
    if (scalable) {
      float sx = 1;
      float sy = 1;
      if (x1 == x2 && y1 == y2) {
	sx = 0;
	sy = 0;
      } else {
	if (xmax != xmin) {
	  sx = Math.abs(x2 - x1) / (xmax - xmin);
	}
	if (ymax != ymin) {
	  sy = Math.abs(y2 - y1) / (ymax - ymin);
	}
      }

      posx = (posx - xmin) * sx + x0;
      posy = (posy - ymin) * sy + y0;
      pos.setLocation(posx, posy);

      DrawAttr attr = getDrawAttr();
      if (attr != null) {
	float s = sy; // Math.min(sx, sy);
	attr.scaleFontSize(s);
      }

      if (debug) {
	System.out.println("TextShape.setEnds() @2 string=\"" + string.toString() + 
			   "\" sx=" + sx + " sy=" + sy + 
			   " x1=" + p1.getX() + " y1=" + p1.getY() + 
			   " x2=" + p2.getX() + " y2=" + p2.getY());
      }

    } else {
      posx = posx - xmin + x0;
      posy = posy - ymin + y0;
      pos.setLocation(posx, posy);
    }

    calculateBounds();
  }

  final public int length() {
    return string.length();
  }

  @Override
  public Point2D getRotationAnchor() {
    return pos;
  }

  public void setTextAttr(String name, int style, int size, boolean underlined) {
    DrawAttr attr = getDrawAttr();
    if (attr == null) {
      attr = new DrawAttr();
      setDrawAttr(attr);
    }
    attr.setFont(name, style, size);
    attr.setUnderlined(underlined);
  }

  @Override
  public void draw(Graphics2D g2) {
    //System.out.println("TextShape.draw() text=" + getText() + " prompt=" + prompt); 

    float x = (float) bounds.getX();
    float y = (float) bounds.getY();
    float w = (float) bounds.getWidth();
    float h = (float) bounds.getHeight();
    Shape shape = new Rectangle2D.Float(x, y, w, h);
    if (filled) {
      fillBackground(g2, x, y, w, h, shape);
    }

    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      g2.setFont(attr.getFont());
    } else {
      g2.setFont(DrawAttr.getDefaultFont());
    }

    if (string.length() == (lenPrefix + lenSuffix) && prompt != null) {
      Color c = g2.getColor();
      int r = c.getRed();
      int g = c.getGreen();
      int b = c.getBlue();
      Color c2 = new Color(r, g, b, 90);
      g2.setColor(c2);
      String str;
      if (prefix != null) {
	str = prefix + prompt;
      } else {
	str = prompt;
      }
      if (suffix != null) {
	str += suffix;
      }
      g2.drawString(str, (int) pos.getX(), (int) pos.getY());
      drawUnderline(g2);
      g2.setColor(c);
    } else {
      drawString(g2);
    }
  }

  protected void drawString(Graphics2D g2) { 
    if (!markString) { 
      g2.drawString(string.toString(), (int) pos.getX(), (int) pos.getY());
      drawUnderline(g2);   
    } else {
      int pstart = -1, pend = -1;
      if (parent != null) { 
	DrawShapeMessage message = parent.getMessage();
	if (message != null) { 
	  pstart = message.getSymbolStart();
	  pend = message.getSymbolEnd();
	}
      }
      TextMarker.drawString(g2, string.toString(), (int) pos.getX(), (int) pos.getY(),
			    pstart, pend);
    }   
  }

  @Override
  public void drawOverHint(Graphics2D g2) {
    FontMetrics fm = getFontMetrics();
    int ascent = fm.getAscent();
    int descent = fm.getDescent();
    float x = (float) pos.getX();
    float y = (float) pos.getY();
    float w = width;
    float x1 = x;
    float y1 = y - ascent - 1;
    float h = ascent + descent + 2;
    g2.fill(new Rectangle2D.Float(x1, y1, w, h));
  }

  protected void drawUnderline(Graphics2D g2) {
    if (attr != null && attr.isUnderlined()) {
      float x = (float) pos.getX();
      float y = (float) pos.getY();
      float d = 1;
      g2.draw(new Line2D.Float(x, y + d, x + width, y + d));
    }
  }

  @Override
  public void drawFrame(Graphics2D g2) {
    // called when drawing the selected shape 
    //System.out.println("TextShape.drawFrame() text=" + getText() + " prompt=" + prompt); 
    int penSize = 1;
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      penSize = attr.penSize;
    }

    float x1 = (float) pos.getX();
    float y1 = (float) pos.getY();
    float w = width;
    float h = height;
    if (drawBoxFrame) {
      DrawingUtilities.paintBlurFrame(g2, new Rectangle2D.Float(x1 - 2, y1 - ascent, w + 4, h));
    } else {
      if (string.length() == 0) {
	w = 1;
      }
      DrawingUtilities.drawLineFrame(g2, x1, y1, x1 + w, y1, 
				     penSize + 2 * (int) d, true, false);
    }

    if (showcursor) {
      drawCursor(g2);
      /*
      // for blinking cursor 
      long t = System.currentTimeMillis();
      if ((t / 1000) % 2 == 0) { 
	drawCursor(g2);
      }
      */
    }

  }

  @Override
  public void drawCursor(Graphics2D g2) {
    FontMetrics fm = getFontMetrics();
    int ascent = fm.getAscent();
    int descent = fm.getDescent();
    String prefix = string.substring(0, cursor);
    int dx = fm.stringWidth(prefix);
    Color save_color = g2.getColor();
    g2.setColor(cursorcolor);
    float x = (float) pos.getX();
    float y = (float) pos.getY();
    float x1 = dx;
    float y1 = -ascent - 1;
    float h = descent + ascent + 1;
    g2.fill(new Rectangle2D.Float(x + x1, y + y1, 2, h));
    g2.setColor(save_color);
  }

  @Override
  public int isInside(float px, float py) {
    int result = PointOutside;
    float x = (float) pos.getX();
    float y = (float) pos.getY();
    float x1 = (float) bounds.getX();
    float y1 = (float) bounds.getY();
    float x2 = (float) (bounds.getX() + bounds.getWidth());
    float y2 = (float) (bounds.getY() + bounds.getHeight());
    if (at == null) {
      if ((px >= x1 - 2.0f) && (px <= x2 + 2.0f) && (py >= y1 - 2.0f)
	  && (py <= y2 + 2.0f)) {
	result = PointInside;
	if (string.length() > 0) {
	  if (isNearPoint(x, y, px, py)) {
	    result = PointOnStringBaselineStart;
	  }
	  if (isNearPoint(x + width, y, px, py)) {
	    result = PointOnStringBaselineEnd;
	  }
	}
      }
    } else {
      float tx = px - x;
      float ty = py - y;
      float[] src = { tx, ty };
      float[] dst = new float[2];
      if (inv != null) {
	inv.transform(src, 0, dst, 0, 1);
	float dstx = dst[0];
	float dsty = dst[1];
	if (dstx >= 0 && dstx <= width && dsty >= -ascent && dsty <= descent) {
	  result = PointInside;
	  if (string.length() > 0) {
	    if (isNearPoint(0, 0, dstx, dsty)) {
	      result = PointOnStringBaselineStart;
	    }
	    if (isNearPoint(width, 0, dstx, dsty)) {
	      result = PointOnStringBaselineEnd;
	    }
	  }
	}
      } else {
	if (isNearPoint(0, 0, tx, ty)) {
	  result = PointOnStringBaselineStart;
	}
      }
    }
    return result;
  }

  @Override
  public boolean isZeroSize() {
    return (string.length() == 0 && prompt == null);
    //return (string.length() == 0);
  }

  public int getWidth() {
    return width;
  }

  public int getHeight() {
    return height;
  }

  public boolean isMarkString() {
    return markString;
  }

  public void setMarkString(boolean b) {
    markString = b;
  }

  @Override
  public void fitComponents() {
    calculateBounds();
  }

  @Override
  public void doLayout() {
    calculateBounds();
  }

  @Override
  public void calculateBounds() {
    FontMetrics fm = getFontMetrics();
    if (fm == null) {
      bounds.setRect(pos.getX(), pos.getY(), 0, 0);
      p1.setLocation(pos.getX(), pos.getY());
      p2.setLocation(pos.getX(), pos.getY());
      width = height = 0;
      ascent = descent = leading = 0;
    } else {
      ascent = fm.getAscent();
      descent = fm.getDescent();
      leading = fm.getLeading();
      String str;
      if (string.length() == (lenPrefix + lenSuffix) && prompt != null) {
	if (prefix != null) {
	  str = prefix + prompt;
	} else {
	  str = prompt;
	}
	if (suffix != null) {
	  str += suffix;
	}
      } else {
	str = string.toString();
      }
      width = fm.stringWidth(str) + 1;
      height = ascent + descent + leading;
      if (at == null) {
	bounds.setRect(pos.getX(), pos.getY() - ascent - 1, width, height);
	framebounds.setRect(pos.getX() - d, pos.getY() - ascent - 1 - d, 
			    width + 2 * d, height + 2 * d);
      } else {
	float[] src = { 0, -ascent, 0, descent, width, -ascent, width, descent };
	float[] dst = new float[8];
	at.transform(src, 0, dst, 0, 4);
	float xMin, xMax, yMin, yMax;
	xMin = xMax = dst[0];
	yMin = yMax = dst[1];
	for (int i = 1; i <= 3; i++) {
	  float x = dst[2 * i];
	  float y = dst[2 * i + 1];
	  xMin = Math.min(x, xMin);
	  xMax = Math.max(x, xMax);
	  yMin = Math.min(y, yMin);
	  yMax = Math.max(y, yMax);
	}
	bounds.setRect(pos.getX() + xMin - 1, pos.getY() + yMin - 1, 
		       xMax - xMin + 1, yMax - yMin + 1);
	framebounds.setRect(pos.getX() + xMin - 1 - d, pos.getY() + yMin - 1 - d, 
			    xMax - xMin + 1 + 2 * d, yMax - yMin + 1 + 2 * d);
      }
      p1.setLocation(bounds.getX(), bounds.getY());
      p2.setLocation(bounds.getX() + bounds.getWidth(), 
		     bounds.getY() + bounds.getHeight());
    }
  }

  public int stringWidth(String s) {
    FontMetrics fm = getFontMetrics();
    if (fm == null) {
      return 0;
    } else {
      return fm.stringWidth(s);
    }
  }

  public FontMetrics getFontMetrics() {
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      return attr.getFontMetrics();
    } else {
      return DrawAttr.getDefaultFontMetrics();
    }
  }

  @Override
  public void setToRotation(float vx, float vy) {
    float x = (float) pos.getX();
    float y = (float) pos.getY();
    double t = DrawingUtilities.calculateRotationAngle(x, y, vx, vy);
    setRotationAngle((float) t);
  }

  public TextShape getPrevLine() {
    return prevLine;
  }

  public void setPrevLine(TextShape l) {
    prevLine = l;
  }

  public TextShape getNextLine() {
    return nextLine;
  }

  public void setNextLine(TextShape l) {
    nextLine = l;
  }

  public void linkAfter(TextShape t) {
    if (t != null) {
      t.nextLine = this;
      prevLine = t;
    }
  }

  public void linkBefore(TextShape t) {
    if (t != null) {
      t.prevLine = this;
      nextLine = t;
    }
  }

  public int getLineNumber() {
    TextShape t = this;
    int ln = 0;
    while (t.prevLine != null) {
      t = t.prevLine;
      ln++;
    }
    return ln;
  }

  public BoxGroup getBoxPrototype() {
    return boxPrototype;
  }

  public void setBoxPrototype(BoxGroup boxPrototype) {
    this.boxPrototype = boxPrototype;
  }

  public String getPrompt() {
    return prompt;
  }

  public void setPrompt(String prompt) {
    this.prompt = prompt;
    calculateBounds();
  }

  public void setDisplayMode(int displayMode) { 
    //System.out.println("TextShape.setDisplayMode() " + displayMode);

    if (displayMode != this.displayMode) { 
      this.displayMode = displayMode;

      boolean clip = true;
      switch (displayMode) { 
      case POPUP:
	filled = true; 
	clip = false;
	break; 

      case EDITING:
	filled = true; 
	clip = false;	
	break;
	
      case NORMAL:
      default:
	filled = false; 
	clip = true;
	break; 
      }

      DrawShape c = parent;
      while (c != null && 
	     c instanceof Label) { 
	c.setClipSubShapes(clip);
	c = c.getParent();
      }
      if (c != null) { 
	c.setClipSubShapes(clip);
      }
    }
  }

  static class Mark {
    Mark(int start, int end, int style) { 
      this.start = start;
      this.end = end;
      this.style = style; 
    }
    int start, end, style;
  }

  static class Style { 
    Style() {} 
    Style(Color c) { 
      this.color = c;
    }
    Style(Color c, boolean b, boolean i, boolean u) { 
      this.color = c;
      this.bold = b;
      this.italic = i;
      this.underline = u; 
    }
    Color color;
    boolean bold, italic;
    boolean underline; 
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("x", pos.getX());
    attrs.put("y", pos.getY());
    attrs.put("mark", markString);
    attrs.put("multiline", allowMultiLine);
    return attrs;
  }

  @Override
  protected void writeXMLElements(CodeWriter out, int mode,
				  String namespacePrefix) {
    if (out != null) {
      super.writeXMLElements(out, mode, namespacePrefix);

      writeXMLFont(out, namespacePrefix);

      String pp = "";
      if (prefix != null && prefix.length() > 0) {
	pp += (" " + getXMLAttributeString("prefix", prefix));
      }
      if (suffix != null && suffix.length() > 0) {
	pp += (" " + getXMLAttributeString("suffix", suffix));
      }

      String textnodename;
      textnodename = getPrefixedName("Text", namespacePrefix);
      out.println("<" + textnodename + pp + ">" + filterXMLText(getString()) + 
		  "</" + textnodename + ">");
      if (prompt != null) {
	textnodename = getPrefixedName("Prompt", namespacePrefix);
	out.println("<" + textnodename + ">" + filterXMLText(prompt) + 
		    "</" + textnodename + ">");
      }
    }
  }

  @Override
  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
    if (e != null) {
      String nodename = e.getLocalName();
      if ("Text".equals(nodename) || "Prompt".equals(nodename)) {
	String text = XMLUtil.getTextChildNodeValue(e);
	if ("Text".equals(nodename)) {
	  string.delete(0, string.length());

	  prefix = e.getAttribute("prefix");
	  if (prefix != null) {
	    lenPrefix = prefix.length();
	    string.append(prefix);
	  }

	  if (text != null) {
	    string.append(text);
	  }

	  suffix = e.getAttribute("suffix");
	  if (suffix != null) {
	    lenSuffix = suffix.length();
	    string.append(suffix);
	  }
	} else {
	  prompt = text;
	}
      } else {
	super.processXMLElement(e, namespaceURI);
      }
    }
  }

  @Override
  public void setAttrFromXML(org.w3c.dom.Element node, String namespaceURI) {
    if (node != null) {
      String uri = node.getNamespaceURI();
      if (namespaceURI == null || namespaceURI.equals(uri)) {
	super.setAttrFromXML(node, namespaceURI);

	String xs, ys;
	float x, y;
	xs = node.getAttribute("x");
	ys = node.getAttribute("y");
	try {
	  x = Float.parseFloat(xs);
	  y = Float.parseFloat(ys);
	  if (pos != null) {
	    pos.setLocation(x, y);
	  } else {
	    pos = new Point2DFloat(x, y);
	  }
	} catch (NumberFormatException ex) {
	}
	String s;
	s = node.getAttribute("mark");
	if (s != null && s.length() > 0) { 
	  markString = Boolean.valueOf(s);
	}
	s = node.getAttribute("multiline");
	if (s != null && s.length() > 0) { 
	  allowMultiLine = Boolean.valueOf(s);
	}
      }
      calculateBounds();
    }
  }

  @Override
  public String toString() {
    return super.toString() + " text=\"" + string.toString() + "\"";
  }

  protected StringBuilder string; // includes prefix and suffix, if any 

  protected String prefix = null;

  protected String suffix = null;

  protected boolean markString = false; 

  protected int lenPrefix = 0;

  protected int lenSuffix = 0;

  protected Point2D pos; // the baseline left point of the string

  protected int width = 0, height = 0;

  protected int ascent = 0, descent = 0, leading = 0;

  protected TextShape prevLine = null, nextLine = null;

  protected BoxGroup boxPrototype = null;

  protected String prompt = null;

  protected boolean allowMultiLine = true;

  protected static final boolean drawBoxFrame = true;

  protected static final boolean debug = false;

}
